/*
 * dsm.c
 *
 *  Created on: Apr 9, 2011
 *      Author: omkar
 */
#include "dsm.h"
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/mman.h>
#include<signal.h>
#include<pthread.h>
#include<errno.h>

pthread_t listener_thread;

int listener_socket;
int connection_socket;
struct sockaddr_in master_addr;
struct sockaddr_in other_addr;
struct sockaddr_in listener_addr;
char *master_ip;
char *other_ip;
int master_port;
int other_port;

void *v_start; // virtual memory starting address
int pagesize;
int v_mem_pages; // number of pages in virtual memory

int is_master;

// some variables used by listener thread
int sock_size;
int which_page;
char* temp;
int rc;
char c;
int bytes;

/*#######################################################################*
 *  Listener thread which accepts page requests and sends back the page  *
 *#######################################################################*/
void listener()
{
	listen(listener_socket, 9024);
	while (1)
	{
		if (is_master == 1)
		{
			connection_socket = accept(listener_socket, (struct sockaddr *) &listener_addr, &sock_size);
		}
		else
		{
			connection_socket = accept(listener_socket, (struct sockaddr *) &listener_addr, &sock_size);
		}

		temp = (char*) &which_page;
		rc = 0;
		bytes = 1;
		// receive the number of page to be sent
		while (1)
		{
			if ((rc = recv(connection_socket, &c, 1, 0)) == 1)
			{
				*temp++ = c;
				bytes++;
				if (bytes >= sizeof(int))
				{
					break;
				}
			}
		}

		// keep page readable so that we can send it
		mprotect(v_start + (which_page * pagesize), pagesize, PROT_READ);
		// send the page
		send(connection_socket, v_start + (which_page * pagesize), pagesize, 0	);
		// disown the page
		mprotect(v_start + (which_page * pagesize), pagesize, PROT_NONE);
		close(connection_socket);
	}
	close(listener_socket);
}

/*####################################################################*
 *    Segmentation fault handler, requests page from other process    *
 *####################################################################*/
void handler(int sig, siginfo_t *info, void *context)
{
	void * source = info->si_addr;
	int which_page = (((void*)source) - ((void*)v_start)) / pagesize;
	int send_request_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (is_master == 1)
	{
		int ret1 = connect(send_request_socket, (struct sockaddr *) &other_addr, sizeof(other_addr));
		if(ret1 != 0)
		{
			printf("ret1=%d\n",ret1);
		}
	}
	else
	{
		int ret2 = connect(send_request_socket, (struct sockaddr *) &master_addr, sizeof(master_addr));
		if(ret2 !=0)
		{
			printf("ret2=%d\n", ret2);
		}
	}
	// send the page number to other process
	int st = send(send_request_socket, &which_page, sizeof(int), 0);
	mprotect(v_start + (which_page * pagesize), pagesize, PROT_WRITE);
	char *r = v_start + (which_page * pagesize);
	int req = pagesize;
	int i = 0;
	char * r1;
	char * buf = (char*) malloc(pagesize);
	int rc = 0;

	// Now receive the entire page from other process
	while (1)
	{
		r1 = buf;
		rc = 0;
		rc = recv(send_request_socket, buf, req, 0);
		for (i = 0; i < rc; i++)
		{
			*r++ = *r1++;
		}
		req = req - rc;
		if (req <= 0)
		{
			close(send_request_socket);
			break;
		}
	}
	mprotect(v_start + (which_page * pagesize), pagesize, PROT_READ | PROT_WRITE);
}

void destroyDSM()
{
	close(listener_socket);
}

void initializeDSM(int ismaster, char * masterip, int mport, char *otherip, int oport, int numpagestoalloc)
{
	connection_socket = socket(AF_INET, SOCK_STREAM, 0);
	sock_size = sizeof(struct sockaddr_in);

	// system parameters
	is_master = ismaster;
	master_ip = masterip;
	other_ip = otherip;
	master_port = mport;
	other_port = oport;
	v_mem_pages = numpagestoalloc;

	// set the segmentation fault signal handler
	struct sigaction sig_action;
	memset(&sig_action, 0, sizeof(struct sigaction));
	sig_action.sa_handler = (void (*)) handler;
	sigfillset(&sig_action.sa_mask);
	sig_action.sa_flags = SA_SIGINFO;
	sigaction(SIGSEGV, &sig_action, NULL);

	// map the virtual memory
	pagesize = sysconf(_SC_PAGE_SIZE);
	v_start = (char*) mmap((void *) 0xf9f98000, numpagestoalloc * pagesize, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_FIXED|MAP_PRIVATE, 0, 0);

	if (is_master == 1)
	{
		mprotect(v_start, v_mem_pages * pagesize, PROT_READ | PROT_WRITE);
	}
	else
	{
		mprotect(v_start, v_mem_pages * pagesize, PROT_NONE);
	}

	// listener_address used for listening for page requests
	memset(&listener_addr, '\0', sizeof(listener_addr));
	listener_addr.sin_family = AF_INET;
	listener_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	listener_addr.sin_port = htons(0);

	// set the sockets parameters
	listener_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (is_master == 1)
	{
		// in master, other_addr will be destination address for page requests
		memset(&other_addr, '\0', sizeof(other_addr));
		other_addr.sin_family = AF_INET;
		other_addr.sin_addr.s_addr = inet_addr(other_ip);
		other_addr.sin_port = htons(other_port);

		// listener_socket handles page requests
		memset(&master_addr, '\0', sizeof(master_addr));
		master_addr.sin_family = AF_INET;
		master_addr.sin_addr.s_addr = inet_addr(master_ip);
		master_addr.sin_port = htons(master_port);
		int ret1 = bind(listener_socket, (struct sockaddr *) &master_addr, sizeof(master_addr));
		if (ret1 != 0)
		{
			printf("Use some other port\n", master_port);
			exit(1);
		}
	}
	else
	{
		// in other, the master_addr will be destination address for page requests
		memset(&master_addr, '\0', sizeof(master_addr));
		master_addr.sin_family = AF_INET;
		master_addr.sin_addr.s_addr = inet_addr(master_ip);
		master_addr.sin_port = htons(master_port);

		// listener socket handles page requests
		memset(&other_addr, '\0', sizeof(other_addr));
		other_addr.sin_family = AF_INET;
		other_addr.sin_addr.s_addr = inet_addr(other_ip);
		other_addr.sin_port = htons(other_port);
		int ret2 = bind(listener_socket, (struct sockaddr *) &other_addr, sizeof(other_addr));
		if (ret2 != 0)
		{
			printf("Use some other port\n", other_port);
			exit(1);
		}
	}
	pthread_create(&listener_thread, NULL, (void (*))listener, NULL); // start the listener thread
}

void * getsharedregion()
{
	return v_start;
}
